package com.demo.common.swagger.plugin;

import com.demo.common.support.model.DataResponse;
import com.demo.common.support.model.EmptyDataResponse;
import com.demo.common.support.model.MultipleDataResponse;
import com.demo.common.support.model.SimpleDataResponse;
import com.demo.common.support.page.PageResponse;
import com.demo.common.swagger.annotation.ApiDataResponse;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Function;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationModelsProviderPlugin;
import springfox.documentation.spi.service.contexts.RequestMappingContext;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

import java.util.Optional;

/**
 * 对{@link com.demo.common.support.model.DataResponse}泛型支持
 *
 * @author molong
 * @since 2024/6/11
 */
@Order(SwaggerDataResponseProvider.PLUGIN_ORDER)
@SuppressWarnings("all")
public class SwaggerDataResponseProvider implements OperationModelsProviderPlugin {


    private final TypeResolver typeResolver;

    public SwaggerDataResponseProvider(TypeResolver typeResolver) {
        this.typeResolver = typeResolver;
    }

    final static int PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 1001;

    @Override
    public void apply(RequestMappingContext context) {
        ResolvedType resolvedType = context.getReturnType();
        Optional<ApiDataResponse> responseOptional = context.findAnnotation(ApiDataResponse.class);
        if (responseOptional.isPresent() && fromDataRespose(resolvedType)) {
            ApiDataResponse response = responseOptional.get();
            ResolvedType actualResolvedType = alternativeTypeResolver(resolvedType, response.responseType());
            ResolvedType type = context.alternateFor(resolvedTypeFromResponse(typeResolver, actualResolvedType).apply(response));
            context.operationModelsBuilder().addReturn(type);
        }
    }

    private boolean fromDataRespose(ResolvedType resolvedType) {
        return DataResponse.class.isAssignableFrom(resolvedType.getErasedType());
    }

    private ResolvedType alternativeTypeResolver(ResolvedType actualResolvedType, Class<?> type) {
        if (isNotVoid(type) || !isNotVoid(actualResolvedType.getErasedType())) {
            return typeResolver.resolve(type);
        }
        return actualResolvedType;
    }

    private static Optional<ResolvedType> getGenericType(TypeResolver typeResolver, ResolvedType responseType, ApiDataResponse apiDataResponse) {
        if (responseType.getTypeParameters().isEmpty()) {
            return Optional.of(typeResolver.resolve(apiDataResponse.responseType()));
        }
        return Optional.ofNullable(responseType.getTypeParameters().get(0));
    }

    private Function<ApiDataResponse, ResolvedType> resolvedTypeFromResponse(final TypeResolver typeResolver, ResolvedType actualResolvedType) {
        return response -> {
            ResolvedType resolvedType = getGenericType(typeResolver, actualResolvedType, response).orElse(null);
            if (response.pageable()) {
                return typeResolver.resolve(PageResponse.class, resolvedType);
            }
            if (response.collectable()) {
                return typeResolver.resolve(MultipleDataResponse.class, resolvedType);
            }
            if (isNotVoid(resolvedType.getErasedType())) {
                return typeResolver.resolve(SimpleDataResponse.class, resolvedType);
            }
            return typeResolver.resolve(EmptyDataResponse.class);
        };
    }


    @Override
    public boolean supports(DocumentationType delimiter) {
        return SwaggerPluginSupport.pluginDoesApply(delimiter);
    }

    private static boolean isNotVoid(Class<?> response) {
        return Void.class != response
                       && void.class != response;
    }

}

